/*  
    Copyright 2010 Mike Eheler <mike.eheler@gmail.com>

    This file is part of Four Falling Blocks Puzzle Game.

    Four Falling Blocks Puzzle Game is free software: you can
	redistribute it and/or modify it under the terms of the GNU General
	Public License as published by the Free Software Foundation, either
	version 3 of the License, or (at your option) any later version.

    Four Falling Blocks Puzzle Game is distributed in the hope that it
	will be useful, but WITHOUT ANY WARRANTY; without even the implied
	warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
	See the GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Four Falling Blocks Puzzle Game.
	
	If not, see <http://www.gnu.org/licenses/>.
*/

#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <fstream>

#include <boost/filesystem.hpp>

#include "game.h"
#include "debug_console.h"

Game::Game()
  : mCurrentPiece( 0 ),
	mNextPiece( 0 ),
	mGameOver( true )
{
	reset();

	srand( time(0) );

	mHighScores.resize( 10 );
	for( uint8_t i = 0; i < 10; i++ )
	{
		char name[256];
		sprintf( name, "Player %d", i + 1 );
		mHighScores[i].name = std::string( name );
		mHighScores[i].score = 10000 - i * 1000;
	}
	loadHighScores();
	std::sort( mHighScores.begin(), mHighScores.end() );
	std::reverse( mHighScores.begin(), mHighScores.end() );
}

Game::~Game()
{
	if( mCurrentPiece ) delete mCurrentPiece;
	if( mNextPiece )    delete mNextPiece;
	srand( 1 );
}

void Game::reset()
{
	resetPosition();

	mBoard.clear();

	if( mCurrentPiece )
	{
		delete mCurrentPiece;
		mCurrentPiece = 0;
	}
	if( mNextPiece )
	{
		delete mNextPiece;
		mNextPiece = 0;
	}

	mLevel = 1;
	mLines = 0;
	mScore = 0;
	memset( mPieceCounts, 0, sizeof(uint32_t) * 7 );
}

void Game::resetPosition()
{
	mPosX = 3;
	mPosY = -1;
}

Piece * Game::newPiece( uint8_t type )
{
	Piece * result;

	switch( type )
	{
	case 0: result = new PieceI(); break;
	case 1: result = new PieceJ(); break;
	case 2: result = new PieceL(); break;
	case 3: result = new PieceO(); break;
	case 4: result = new PieceS(); break;
	case 5: result = new PieceT(); break;
	case 6: result = new PieceZ(); break;
	default: result = 0; break;
	}

	return result;
}

Piece * Game::randomPiece()
{
	if( mHellMode )
	{
		int piece = rand() % 2;
		return newPiece( piece * 2 + 4 );
	}
	else
		return newPiece( rand() % 7 );
}

void Game::newGame( bool hellMode )
{
	reset();

	mHellMode = hellMode;
	mCurrentPiece = randomPiece();
	mNextPiece = randomPiece();
	mPieceCounts[mCurrentPiece->type()]++;
	mGameOver = false;
}

void Game::endGame()
{
	mGameOver = true;
}

void Game::setHighScore( uint8_t pos, HighScore const & score )
{
	if( pos < mHighScores.size() )
		mHighScores[pos] = score;
	std::sort( mHighScores.begin(), mHighScores.end() );
	std::reverse( mHighScores.begin(), mHighScores.end() );
}

void Game::loadHighScores()
{
	char homedir[1024];
	#ifdef _WIN32
	ExpandEnvironmentStringsA( "%UserProfile%", homedir, 1024 );
	#endif

	boost::filesystem::path config_path( std::string(homedir) + "/.4fb/high_scores" );
	if( boost::filesystem::exists(config_path) )
	{
		std::ifstream file( config_path.file_string().c_str() );
		if( file.is_open() )
		{
			uint8_t i = 0;
			char buffer[1024];

			while( !file.eof() && i < 10 )
			{
				file.getline( buffer, 1024 );
				if( !file.eof() )
				{
					mHighScores[i].name = std::string(buffer);
					file.getline( buffer, 1024 );
					mHighScores[i].score = atoi(buffer);
				}
				i++;
			}

			file.close();
		}
	}
}

void Game::saveHighScores() const
{
	char homedir[1024];
	#ifdef _WIN32
	ExpandEnvironmentStringsA( "%UserProfile%", homedir, 1024 );
	#endif

	boost::filesystem::path config_path( std::string(homedir) + "/.4fb/" );
	bool dir_exists = boost::filesystem::exists(config_path) || boost::filesystem::create_directory(config_path);
	if( dir_exists )
	{
		std::ofstream file( std::string(config_path.file_string() + "high_scores").c_str(), std::ios::out );
		if( file.is_open() )
		{
			for( uint8_t i = 0; i < 10; i++ )
			{
				char buffer[1024];
				_itoa( mHighScores[i].score, buffer, 10 );

				file << std::string(mHighScores[i].name) << std::endl;
				file << std::string(buffer) << std::endl;
			}
			file.close();
		}
	}
}

void Game::rotateLeft()
{
	if( !gameOver() && currentPiece() )
	{
		Piece * test = newPiece( currentPiece()->type() );
		*test = *currentPiece();
		test->rotateLeft();
		if( board()->pieceInBounds(test,mPosY,mPosX) &&
			!board()->pieceCollides(test,mPosY,mPosX) )
			*currentPiece() = *test;
		delete test;
	}
}

void Game::rotateRight()
{
	if( !gameOver() && currentPiece() )
	{
		Piece * test = newPiece( currentPiece()->type() );
		*test = *currentPiece();
		test->rotateRight();
		if( board()->pieceInBounds(test,mPosY,mPosX) &&
			!board()->pieceCollides(test,mPosY,mPosX) )
			*currentPiece() = *test;
		delete test;
	}
}

void Game::drop()
{
	if( !gameOver() && currentPiece() )
	{
		int8_t pre_drop_pos = mPosY;
		if( mPosY == -1 ) advance();
		while( mPosY != -1 )
			advance();
		mScore += 20 - pre_drop_pos;
	}
}

void Game::advance()
{
	if( gameOver() )
		return;

	if( board()->pieceInBounds(currentPiece(), mPosY + 1, mPosX) &&
		!board()->pieceCollides(currentPiece(), mPosY + 1, mPosX) )
		mPosY++;
	else if( mPosY == -1 ) // piece piece can't move
		endGame();
	else
	{
		board()->placePiece( currentPiece(), mPosY, mPosX );
		delete mCurrentPiece;

		uint8_t cleared, consecutive;
		board()->clearRows( cleared, consecutive );
		mScore += mPosY + cleared * 100;
		if( consecutive == 4 ) mScore += 400;
		mLines += cleared;
		uint32_t old_level = mLevel;
		mLevel = mLines / 10 + 1;
		mLevelChanged = mLevel != old_level;

		mCurrentPiece = mNextPiece;
		mNextPiece = randomPiece();
		mPieceCounts[mCurrentPiece->type()]++;
		resetPosition();
	}
}

void Game::moveLeft()
{
	if( gameOver() )
		return;

	if( board()->pieceInBounds(currentPiece(), mPosY, mPosX - 1) &&
		!board()->pieceCollides(currentPiece(), mPosY, mPosX - 1) )
		mPosX--;
}

void Game::moveRight()
{
	if( gameOver() )
		return;

	if( board()->pieceInBounds(currentPiece(), mPosY, mPosX + 1) &&
		!board()->pieceCollides(currentPiece(), mPosY, mPosX + 1) )
		mPosX++;
}
